﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace Quakk.Twitter
{
    public abstract class Timeline
    {
        private const string urlPrefix = "http://s3.amazonaws.com/twitter_production/profile_images/";

        private readonly TwitterApi twitterApi;
        private readonly List<string> missingImages = new List<string>();
        protected string xmlRootElementName = "status";

        protected TwitterApi TwitterApi
        {
            get
            {
                return twitterApi;
            }
        }

        protected string Url
        {
            get; set;
        }

        protected string FileName
        {
            get; set;
        }

        protected string AppPath
        {
            get; set;
        }

        public List<Tweet> Tweets
        {
            get; set;
        }

        protected Timeline(TwitterApi twitterApi)
        {
            this.twitterApi = twitterApi;
            AppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Quakk");
        }

        /// <summary>
        /// Update the Tweet collection from Twitter
        /// </summary>
        public void Update()
        {
            if(Url == null)
            {
                throw new InvalidOperationException("Url is not set.");
            }

            string xml = TwitterApi.OpenUrl(Url, true);

            List<Tweet> tweets = new List<Tweet>();
            tweets.AddRange(ParseTimelineXml(xml));
            Tweets = tweets;

            Save();
        }

        /// <summary>
        /// Load the Tweet collection stored locally
        /// </summary>
        public void Load()
        {
            if(FileName == null)
            {
                throw new InvalidOperationException("The FileName is not set.");
            }

            List<Tweet> tweets = new List<Tweet>();
            string filePath = Path.Combine(AppPath, FileName);
            XmlSerializer serializer = new XmlSerializer(typeof(List<Tweet>));
             
            if (File.Exists(filePath))
            {
                FileStream fileStream = null;

                try
                {
                    fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read);
                    Debug.WriteLine(fileStream.Length);
                    tweets = (List<Tweet>)serializer.Deserialize(fileStream);
                }
                finally
                {
                    if (fileStream != null)
                    {
                        fileStream.Close();
                    }
                }
            }

            // The images have to be loaded seperately
            foreach (Tweet tweet in tweets)
            {
                tweet.UserImage = GetUserImage(tweet.UserImageUrl);
            }

            Tweets = tweets;
        }

        /// <summary>
        /// Saves the current Tweet collection locally
        /// </summary>
        public void Save()
        {
            if (FileName == null)
            {
                throw new InvalidOperationException("The FileName is not set.");
            }

            XmlSerializer serializer = new XmlSerializer(typeof(List<Tweet>));

            Stream stream = null;
            try
            {
                stream = File.Create(Path.Combine(AppPath, FileName));
                serializer.Serialize(stream, Tweets);
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }
            }            
        }

        /// <summary>
        /// Downloads any images that weren't stored locally when the Tweets were Loaded or Updated
        /// </summary>
        /// <exception cref="TwitterResponseException"></exception>
        public void DownloadMissingImages()
        {
            // Download any missing images and save them locally
            foreach (string url in missingImages)
            {
                string filePath = GetLocalImagePathFromUrl(url);
                try
                {
                    TwitterApi.DownloadImage(url, filePath);
                }
                catch (TwitterResponseException)
                { }
            }

            // Now load any images that are missing from the tweets
            if (Tweets != null)
            {
                foreach (Tweet tweet in Tweets)
                {
                    if (tweet.UserImage == null)
                    {
                        tweet.UserImage = GetUserImage(tweet.UserImageUrl);
                    }
                }
            }
        }

        /// <summary>
        /// Parses a Twitter Timeline Xml file into an array of Tweet objects
        /// </summary>
        /// <param name="xml">The Xml returned from Twitter</param>
        /// <returns>An array of Tweets</returns>
        private Tweet[] ParseTimelineXml(string xml)
        {
            if (xml == "")
            {
                return null;
            }

            XmlDocument document = new XmlDocument();

            try
            {
                document.LoadXml(xml);                
            }
            catch(XmlException ex)
            {
                throw new TwitterResponseException("Twitter returned malformed Xml response.", ex);
            }

            XmlNodeList rootNodes = document.GetElementsByTagName(xmlRootElementName);

            // Build a Tweet object for each element in the Xml
            int i = 0;
            Tweet[] tweetArray = new Tweet[rootNodes.Count];
            foreach (XmlNode node in rootNodes)
            {
                Tweet tweet = new Tweet(node, TwitterApi.Username);
                tweet.UserImage = GetUserImage(tweet.UserImageUrl);
                tweetArray[i++] = tweet;
            }

            return tweetArray;
        }

        /// <summary>
        /// Opens the locally stored image that matches the Url provided,
        /// if the image doesn't exist locally then it is flagged to be downloaded
        /// </summary>
        /// <param name="url">The Twitter image url</param>
        /// <returns>The image, if available locally otherwise null</returns>
        private Image GetUserImage(string url)
        {
            string fileName = GetLocalImagePathFromUrl(url);

            // TODO: Consider caching these images in a list rather than loading them each time
            if (File.Exists(fileName))
            {
                try
                {
                    return new Bitmap(fileName);
                }
                catch(InvalidOperationException)
                { }
            }

            missingImages.Add(url);
            return null;
        }

        /// <summary>
        /// Takes a Twitter image Url and returns a local path and filename for that image
        /// </summary>
        /// <param name="url">The Twitter Url of the image</param>
        /// <returns>The local path and filename</returns>
        private string GetLocalImagePathFromUrl(string url)
        {
            string imagesPath = Path.Combine(AppPath, "ProfileImages");

            if (!Directory.Exists(imagesPath))
            {
                Directory.CreateDirectory(imagesPath);
            }

            string fileName = url.Substring(urlPrefix.Length).Replace("/", "-");
            return Path.Combine(imagesPath, fileName);
        }
    }
}
